<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <div>打开控制台查看结果</div>

    <script>
        console.log('================Generator============');
        /* 
           概念：Generator函数是ES6提供的一种一部变成解决方案
           Generator函数是一个状态机，封装了多个内部状态。
           执行Generator函数会返回一个遍历器对象，也就是说Generator还是一个遍历器对象生成函数。
           返回的遍历器对象可以一次遍历Generator函数内部的每一个状态。
        */

        /* 
           表面上，Generator函数是一个普通函数，但它有两个特征。
           一：functiong关键字与函数名之间有一个星号。
           二：函数内部使用yield表达式，定义不同的内部状态。（yield翻译为产出）
        */
         function* hello(){
            yield 'hello';
            yield 'world';
            return 'hi'
         }
         let h = hello()
         console.log(h);  // hello {<suspended>}
         /* 上面代码定义了Generator函数hello，函数内部有两个yield表达式（hello、world),那么该函数就有三个状态：hello、world、return语句（结束执行） */
         
         /* 表面上看Generator在写法上与普通的函数一样，但实际上，Generator函数调用后并不会执行，而且返回的也不是函数运行结果，而是指向内部状态的指针对象，也就是遍历器对象（Iterator Object)
            
            调用遍历器对象next方法，使指针移向下一个状态，每次调用next，内部指针就从函数头部或者上一次停下来的地方开始执行，直到遇到下一个yield或return为止。也就是说，Generator函数是分段执行的，yield表达式是暂停执行的标记，而next方法可以恢复执行。
         */
        console.log(h.next());  // {value: 'hello', done: false}
        console.log(h.next());  // {value: 'world', done: false}
        console.log(h.next());  // {value: 'hi', done: true}
        console.log(h.next());  // {value: undefined, done: true}
        /* 调用 Generator 函数，返回一个遍历器对象，代表 Generator 函数的内部指针。以后，每次调用遍历器对象的next方法，就会返回一个有着value和done两个属性的对象。value属性表示当前的内部状态的值，是yield表达式后面那个表达式的值；done属性是一个布尔值，表示是否遍历结束。 */

        // Generator函数的星号并没有规定位置，所以下面的几种写法都可以
        function* f(){}  // 最常用的
        function * f(){}
        function *f(){}
        function*  f(){}

        console.log('=============yield表达式================');
        /* 
           Generator函数返回遍历器对象，只有调用next方法才会遍历下一个内部状态。
           
           1、其实，yield表达式就是一个暂停表示，当next执行后，遇到yield表达式会暂停执行后面的操作，紧跟在yield后面的表达式并返回当前遍历对象的value属性值。也就是说，yield这一行后面的表达式结果会作为属性对象被返回。

           2、下一次调用next方法时，再继续往下执行，直到遇到下一个yield表达式。

           3、如果没有遇到新的yield表达式，就一直运行到函数结束，直到return语句为止，并将return语句后的表达式值返回。

           4、如果函数内没有return，则返回的对象value属性值为undefined

           注意：yield表达式后的表达式，只有当调用next方法、内部指针指向该语句时才会执行，也就相当于js提供了手动的“惰性求值”语法功能。
        */

        /* yield后的表达式立即求值，只有next方法将指针移到这一句时才会求值。 */
       function* sum(){
        yield 1+2;
       }
       console.log(sum());  // sum {<suspended>}
       console.log(sum().next());  // {value: 3, done: false}

       /* 
          yield与return的区别：

          相同：
          1、都能使函数暂停执行
          2、都能返回紧跟在语句后面的表达式的值。

          不同：
          1、yield具有位置记忆功能，return没有
          2、一个函数内，只能执行一次（一个）return语句，而yield表达式可以执行多次（多个）
          3、正常函数只能执行一次return，（执行return后函数停止），而Generator函数可以返回一系列值，因为可以有任意多个yield。
       */

       /* Genarator内不用yield表达式，这时就变成了一个单纯的暂缓执行函数。 */
       function* fn() {
          console.log('执行了11')
          console.log('执行了22')
        }
        fn();  // 没有任何执行
        fn().next()  // 执行了11  执行了22

        /* yield只能再在Generator函数内，用在其它地方会报错 */
        function fn2(){
            // yield 100;  // 报错 Unexpected number 
        }

      /* yeild表达式如果用在另一个表达式中，必须放在圆括号里 */
      function* demo(){
         // console.log('hello' + yield); // Uncaught SyntaxError: Unexpected identifier 'yield'
         console.log("hello" + (yield));  // 不报错
      }
      /* 如果yield表达式用作函数参数或放在赋值表达式右边，可以不加括号 */
      function* deom2(){
         foo(yield 'a' , yield 'b');
         let s = yield;
      }

      console.log('=========next方法的参数==========');
      /* yield表达式本身没有返回值，或者说总是返回undefined。next方法可以带一个参数，该参数就会被当作上一个yield表达式返回的值。
         我们可以利用这个特性，来让Generator函数分阶段执行不同的行为。
      */
   //   案例一
   function* f(){
      for(let i = 0;true;i++){
         let reset = yield i;
         if(reset){ i = -1 }
      }
   }
   let g= f()
   console.log(g.next()); // {value: 0, done: false}
   console.log(g.next()); // {value: 1, done: false}
   console.log(g.next()); // {value: 2, done: false}
   console.log(g.next(true)); // {value: 0, done: false}
   console.log(g.next()); // {value: 1, done: false}
   /* 案例一中，定义了一个无限循环的for
   next第一遍，运行到yield这里停止，这里yield后面的i结果为0
   next第二遍，运行到yield这里停止这里yield后面的i结果为2
   前两次next，yield没有参数，那么yield返回的是undefined，直到第三次next，传入一个true
   next第三遍，reset等于true，进入判断更改i的值为-1,遍历的i从-1开始
    */

    /* next可以让Generator函数从暂停状态到恢复运行，Generator的上下文状态是不变的，但是我们可以通过next方法的参数，让Generator函数开始运行时，往内部注入值，让Generator函数在运行的不同阶段，通过该参数调整函数行为。 */
    console.log('案例二');
    function* f2(x){
      let y = 2 * (yield (x + 1));
      let z = yield (y / 3);
      return (x + y + z)
    }
    let a = f2(2)
    console.log(a.next());  // {value: 3, done: false}
    console.log(a.next());  // {value: NaN, done: false}
    console.log(a.next());  // {value: NaN, done: true}
    /* 第一次进来，f2参数x=2,第一个yield中，由于x+1,所以x=3,遇到yield停止
       第二次进来，由于next会从上一个yield停止的地方执行，所以这里执行的是y这一行，next没有参数，所以yield返回undefined，2*undefined = NaN
       第三次进来跟第二次同理
    */

    let b = f2(4)
    console.log(b.next());
    console.log(b.next(9));
    console.log(b.next(20));
    /* 第一次进来， f2参数x=4,第一个yield中，由于x+1,所以x=5,遇到yield停止
       第二次进来，从上次yield停止的地方执行，由于next携带参数，所以y这一行变成了2*9 = 18，到下一行y / 3， 结果：18/3 = 6

       第三次进来，next携带参数20,从z这行开始，z = 20,此时x=5,y=18,x=20,结果：43
    */
   console.log('案例三');
   function* f3(){
      console.log('第一次next:东方不败');
      console.log(`第二次next:${yield}`);
      console.log(`第三次next:${yield}`);
   }
   let c = f3()
   c.next();  // 第一次next:东方不败
   c.next('西方求败'); // 第二次next:西方求败
   c.next('南天门') // 第三次next:南天门




   console.log('=========for...of=======');
   /* for...of可以遍历Generator函数运行时生成的Iterator对象，并且不需要调用next方法
      如果next方法中的done返回true，那么for...of循环就会停止，并且不返回该对象,所以下面的return 5不在for...of循环中。
   */
   function* fo(){
      yield 1;
      yield 2;
      yield 3;
      yield 4;
      return 5;
   }
   for(let i of fo()){
      console.log(i);  // 1 2 3 4
   }

   console.log('=======Generator.prototype.throw()=======');
   /* 
       Generator函数返回的遍历器对象，有一个throw方法，可在函数体外抛出错误，然后再Generator函数体内捕获。
   */
  let s = function* (){
      try {
         yield;
      } catch (e) {
        console.log('内部捕获错误',e);
      }
  };

  let i = s()
  i.next();

   try {
      // i.throw('a');
      // i.throw('b');
      i.throw(new Error('a'));
      i.throw(new Error('b'))
   } catch (e) {
      console.log('外部捕获',e);
   }
   /* 上面代码中，遍历器对象连续抛出了两次错误，第一个throw被Generator函数内的catch捕获。第二次throw的错误由于Generator内的catch已经执行过了，不会再捕捉到这个错误，所以这个错误被抛出了Generator函数外，被外部的catch捕获。
      这里的抛错其实要注意一点，i是遍历器，i抛出的错误是遍历器对象抛出的，而不是全局的throw命令，所以i抛出的错误会在Generator内执行，如果Generator内已经捕捉到了，才会抛出到Generator函数外。
   */



   console.log('=======Generator.prototype.return()======');
   /* Generator函数返回的遍历器对象，有一个return()方法，该方法可以返回给定的值，并终结遍历Generator函数。 */
   function* gen(){
      yield 1;
      yield 2;
      yield 3;
   }
   let ge = gen()
   console.log(ge.next()); // {value: 1, done: false}
   console.log(ge.return('传入参数，结束执行')); // {value: '传入参数，结束执行', done: true}
   console.log(ge.next()); // {value: undefined, done: true}
   /* 如果return()不传值，则返回value的属性为undefined
      如果Generator函数内有try...finally这样的代码块，而且正在执行try代码块，那么return()会让导致即可进入finally代码块，执行完finally代码块后，才返回return的给定的值，整个函数结束。
   */
  function* r(){
   yield 1;
   try {
      yield 2;
      yield 3;
   } finally {
      yield 6;
      yield 7;
   }
   yield 8;
  }
  let r2 = r()
  console.log(r2.next()); // {value: 1, done: false}
  console.log(r2.next()); // {value: 2, done: false}
  console.log(r2.return(10)); // {value: 6, done: false}
  console.log(r2.next());  // {value: 7, done: false}
  console.log(r2.next());  // value: 10, done: true}

  console.log('=====next()、throw()、return()的共同点=======');
  /* 
     next()是将yield表达式替换成一个值。
     throw()是将yield表达式替换成一个throw语句。
     return()是将yield表达式替换成一个return语句。
  */

  console.log('======yield* 表达式======');
  /* 
     如果在Generator函数内部，调用另一个Generator函数，需要在前者的函数体内部手动遍历才能完成。
  */
 function* fo2(){
   yield 'a';
   yield 'b';
 }

 function* fo3(){
   yield 'x';
   for(let i of fo2()) console.log(i);
   yield 'y'
 }

 for(let k of fo3()) console.log(k);  // x a b y
 /* 这样嵌套函数，函数内套函数需要手动遍历，如果有多个Generator函数，写起来非常麻烦；
    因此ES6提供了yield*表达式，用来在一个Generator函数内执行另一个Generator函数；
 */
function* yi(){
   yield 'x';
   yield*  fo2();
   yield 'y'
}
for(let kk of yi()) console.log(kk);  // x a b y

console.log('=====作为对象属性的Generator函数()==========');
/* 如果一个对象的属性是Generator函数，可以简写成下面的形式 */
let obj = {
   * gen(){
      // ...
   }
}
// 或者
let obj2 = {
   gen : function* () {
      // ...
   }
}
// 或者
function* gen(){
   // ...
}
let obj3 = {
   g : gen()
}

console.log('=======Generator函数的this=========');
/* Generator函数总是返回一个遍历器，ES6规定这个遍历器是Generator函数的实例，也继承了Generator函数的prototype对象上的方法。 */
function* aa(){}
aa.prototype.hello = function(){ console.log('hi'); }

let a2 = aa()
a2.hello()  // hi

/* ********** */
function* g5() {
  this.a = 11;
}

let ob = g5();
ob.next();
console.log(ob.a); // undefined

/* Generator函数不能跟new命令一起用，会报错 */
// new aa()  // Uncaught TypeError: aa is not a constructor


/* 让Generator函数返回一个正常的对象实例，既可以用next，又可以获得正常的this */
/* 方法一：
   生成一个空对象，使用call绑定Generator函数内部的this。这样，构造函数调用后，这个空对象就是Generator函数的实例对象。

*/
function* F(){
   this.a = 1;
   yield this.b = 2;
   yield this.c = 3;
}
let o = {}
let f4 = F.call(o)
console.log(f4.next());  // {value: 2, done: false}
console.log(f4.next());  // {value: 2, done: false}
console.log(f4.next());  // {value: undefined, done: true}
console.log( o.a , o.b , o.c );  // 1 2 3

/* 
   但上面这个方法执行的是遍历器对象f，但是生成的对象实例是obj，有没有办法将这两个对象统一呢？
   方法二：将obj换成F.protytype
*/
function* F2(){
   this.a = 1;
   yield this.b = 2;
   yield this.c = 3;
}
let f5 = F2.call(F2.prototype)
console.log(f5.next());  // {value: 2, done: false}
console.log(f5.next());  // {value: 2, done: false}
console.log(f5.next());  // {value: undefined, done: true}
console.log( f5.a , f5.b , f5.c );  // 1 2 3

/* 再将F改造成构造函数，就可以对它执行new命令了 */
function* fun(){
   this.a = 1;
   yield this.b = 2;
   yield this.c = 3;
}
function F3(){
   return fun.call(fun.prototype)
}
let f6 = new F3()

console.log(f6.next());  // {value: 2, done: false}
console.log(f6.next());  // {value: 2, done: false}
console.log(f6.next());  // {value: undefined, done: true}
console.log( f6.a , f6.b , f6.c );  // 1 2 3


console.log('=========Generator的应用===========');
/* 流程控制 */
/* 某些场景下，有多步嵌套操作，前一个函数的返回结果，是下一个函数的初始值
   普通函数的写起来非常麻烦，需要层层嵌套，如下
*/
// step(function (value1){
//    step2(value1,function(value2){
//       step3(value2,function(value3){
//          // ...
//       })
//    })
// })
/* 这种写法可以使用Promise来优化 */
// Promise.resolve(step1)
// .then(step2)
// .then(step3)
// .then(step4)
/* 上面的Promise的链式调用，解决了层层嵌套的问题，但是链式调用看起来非常不美观，而且流程上也不清晰，
   Generator可以进一步优化流程，因为Generator的yield会造成阻塞（暂停执行）下一次next才会继续执行
*/
function* step(value1){
   try {
      let value2 = yield step1(value1);
      let value3 = yield step2(value2);
      let value4 = yield step3(value3);
   } catch {

   }
}
/* 上面的Generator将异步方法变成了同步执行，对比Promise的then()链式调用，Generator则是直接将其改成了同步的风格，每一次结果返回后，进入下一步，到下一个yield阻塞，执行函数，返回，进入下一步。
   其实这个就是async await的一个实现，yield就相当于await，每次yield暂停，直到yield后的函数返回结果（结果内包含next）就进入下一步。
*/





    </script>
</body>
</html>